// Copyright 2012 Google Inc. All Rights Reserved.

package com.google.appengine.syncserver.processor;

import com.google.appengine.syncserver.entities.PreferencesValue;
import com.google.appengine.syncserver.entities.SharedPreferences;
import com.google.appengine.syncserver.shared.PropertyChange;
import com.google.appengine.syncserver.shared.ReadUpdateRequest;
import com.google.appengine.syncserver.shared.ReadUpdateResponsePage;

import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Handles client's request of updates from server.
 * TODO(chintanraoh): there is no transaction roll back here. we need to be careful about that.
 */
public class ReadProcessor extends AbstractProcessor {

  private long getMaxVersion(Set<Entry<String, PreferencesValue>> arrayList) {

    long maxVersion = 0;
    for (Entry<String, PreferencesValue> entry : arrayList) {
      maxVersion = Math.max(maxVersion, entry.getValue().getVersion());
    }

    return maxVersion;
  }

  /**
   * Converts the arguments to an array of preference change.
   *
   * @param changeSet
   * @return Array of property changes.
   */
  private PropertyChange[] getPropertyChangeArray(Set<Entry<String, PreferencesValue>> changeSet) {

    PropertyChange[] propertyChangeArray = new PropertyChange[changeSet.size()];

    int i = 0;
    for (Entry<String, PreferencesValue> entry : changeSet) {
      PreferencesValue prefsValue = entry.getValue();
      propertyChangeArray[i] =
          new PropertyChange(entry.getKey(), prefsValue.getValue(), prefsValue.getDeleted());
      i++;
    }
    return propertyChangeArray;
  }

  private ReadUpdateResponsePage processRead(ReadUpdateRequest req) throws Exception {

    SharedPreferences sharedPreferences = null;

    try {
      // Get the shared preference object from the datastore. The constructor
      // of SharedPreferences loads the data from the datastore.
      sharedPreferences = new SharedPreferences(req.getUsername(), req.getPreferenceName());
      // Get a map of all the properties that have been modified after the client
      // version.
      Map<String, PreferencesValue> sharedPreferenceMap = sharedPreferences.getMapForVersion(
          req.getStartVersionNumber(), req.getResponseSize());

      long maxVersion = req.getStartVersionNumber();
      if (!sharedPreferenceMap.isEmpty()) {
        maxVersion = getMaxVersion(sharedPreferenceMap.entrySet());
      }

      boolean lastPage = false;

      // Check whether there are pages remaining to be sent
      // TODO(chintanraoh): create and use hasValuesGreaterThan(maxVersion)
      // instead.
      if (sharedPreferences.getMapForVersion(maxVersion + 1, Integer.MAX_VALUE).isEmpty()) {
        // Suppose all properties in the datastore have a version that
        // is less than the max version in metadata entity (this is possible,
        // see note below), then we want to send the max version value stored
        // in metadata entity to the client.
        // NOTE: if a property in the datastore has been updated last and then
        // deleted and we clean up the datastore of deleted properties, then we
        // will end up with max version in metadata entity that is greater than
        // max versions of all properties in the datastore.
        maxVersion = sharedPreferences.getMaxVersion();
        lastPage = true;
      }

      return new ReadUpdateResponsePage(
          getPropertyChangeArray(sharedPreferenceMap.entrySet()), maxVersion, lastPage);
    } finally {
      if (sharedPreferences != null) {
        sharedPreferences.rollBackIfActive();
      }
    }
  }

  @Override
  public String processRequest(String requestString) throws Exception {
    ReadUpdateRequest request = ReadUpdateRequest.valueOf(requestString);
    return processRead(request).toString();
  }
}
